package com.henan.provideservice.config.log;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Throwables;
import com.google.common.collect.Sets;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.UriUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;

@Configuration
public class ControllerLogConfiguration {

    @Bean
    public Filter controllerLogFilter() {
        return new ControllerLogFilter();
    }


    @Slf4j
    public static class ControllerLogFilter extends OncePerRequestFilter implements Ordered {
        private static final ObjectMapper JSON = new ObjectMapper();

        private static final Set<MediaType> SUPPORTED_MEDIA_TYPE = Set.of(
                MediaType.valueOf("application/*+json"),
                MediaType.valueOf("application/*+xml"),
                MediaType.valueOf("text/*"),
                MediaType.APPLICATION_FORM_URLENCODED,
                MediaType.APPLICATION_JSON,
                MediaType.APPLICATION_XML,
                MediaType.APPLICATION_NDJSON
        );

        @Override
        public int getOrder() {
            return Ordered.HIGHEST_PRECEDENCE;
        }

        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            long startMills = System.currentTimeMillis();

            ContentCachingRequestWrapper cachingRequest = new ContentCachingRequestWrapper(request);
            ContentCachingResponseWrapper cachingResponse = new ContentCachingResponseWrapper(response);

            try {
                // 执行过滤链
                filterChain.doFilter(cachingRequest, cachingResponse);

                // 提取请求头部信息并记录日志
                Map<String, Set<String>> requestHeaders = extractHeaders(cachingRequest);
                byte[] requestBody = cachingRequest.getContentAsByteArray();

                String uuid = UUID.randomUUID().toString();
                String queryString = cachingRequest.getQueryString();
                String method = cachingRequest.getMethod();
                String url = cachingRequest.getRequestURI() + (null == queryString ? "" : "?" + UriUtils.decode(queryString, StandardCharsets.UTF_8));


                log.info("""
                    
                    >>>>>>>>>>>>>Controller Request Recevied>>>>>>>>>>>>
                    UUID: {}
                    {} {}
                    Headers: {}
                    Body: {}
                    """, uuid, method, url, JSON.writeValueAsString(requestHeaders), parseBody(requestHeaders, requestBody));

                // 提取响应头部信息并记录日志
                Map<String, Set<String>> responseHeaders = extractHeaders(cachingResponse);
                byte[] responseBody = cachingResponse.getContentAsByteArray();

                // 如果响应头没有 Content-Type，设置一个默认值
                if (responseHeaders.get(HttpHeaders.CONTENT_TYPE) == null) {
                    log.warn("Response does not have Content-Type, setting default to application/json");
                    responseHeaders.put(HttpHeaders.CONTENT_TYPE, Collections.singleton(MediaType.APPLICATION_JSON_VALUE));
                }

                long endMills = System.currentTimeMillis();
                double timeTaken = ((double) (endMills - startMills)) / 1000;

                log.info("""
                            
                            >>>>>>>>>>>>>Controller Request Recevied>>>>>>>>>>>>
                            UUID: {}
                            {} {}
                            Headers: {}
                            Body: {}
                            >>>>>>>>>>>>>Controller Response Returned, Time Taken: {} Seconds>>>>>>>>>>>>>
                            Status: {}
                            Headers: {}
                            Body: {}
                            """, uuid, method, url, JSON.writeValueAsString(requestHeaders), parseBody(requestHeaders, requestBody), timeTaken,
                        response.getStatus(), JSON.writeValueAsString(responseHeaders), parseBody(responseHeaders, responseBody));
            } finally {
                // 在最后确保将缓存的响应体写回
                cachingResponse.copyBodyToResponse();
            }
        }

        // 解析请求/响应体
        private String parseBody(Map<String, Set<String>> headers, byte[] content) {
            Set<String> contentTypes = headers.get(HttpHeaders.CONTENT_TYPE);
            if (contentTypes == null || contentTypes.isEmpty()) {
                return "Empty or Unknown Content";
            }

            boolean match = contentTypes.stream()
                    .map(MediaType::valueOf)
                    .anyMatch(c -> SUPPORTED_MEDIA_TYPE.stream().anyMatch(s -> s.includes(c)));

            if (match) {
                try {
                    return new String(content, StandardCharsets.UTF_8);
                } catch (Exception e) {
                    return "Error in body parsing";
                }
            }
            return "Unsupported Content";
        }

        // 提取请求和响应头
        private Map<String, Set<String>> extractHeaders(HttpServletRequest request) {
            Map<String, Set<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
            Enumeration<String> headerNames = request.getHeaderNames();
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                Set<String> values = new HashSet<>(Collections.list(request.getHeaders(name)));
                headers.put(name, values);
            }
            return headers;
        }

        private Map<String, Set<String>> extractHeaders(HttpServletResponse response) {
            Map<String, Set<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
            for (String name : response.getHeaderNames()) {
                Set<String> values = new HashSet<>(response.getHeaders(name));
                headers.put(name, values);
            }
            return headers;
        }
    }


}
